package ext.sim;

import context.ContextThread;
import context.environ.Keys;
import context.environ.Time;
import context.environ.Window;
import context.render.light.Light;
import context.render.tiu.sampler.Sampler;
import context.render.tiu.texture.TexturePNG;
import context.render.vertexdata.VertexData;
import ext.render.RenderSphere;
import ext.render.Renderer;
import ext.sim.forces.Force;
import ext.sim.forces.ForceGrav;
import ext.sim.forces.ForceGravCentered;
import ext.sim.forces.ForceUniformMag;
import ext.sim.forces.ForceViscousDrag;
import ext.sim.forces.ForceVortexAxial;
import ext.sim.forces.ForceVortexPlanar;
import math.math;
import math.mat3;
import math.mat4;
import math.vec2;
import math.vec3;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;

public class Simulation extends ContextThread
{
    
    public static final TexturePNG IMAGE_FACES = new TexturePNG("texture_faces");
    
    private static final double TIME_STEP = 1.0 / 60.0; // Must be > 0.
    
    private static final double N_STEPS = 1; // Must be >= 1.
    
    private static final double SMALL_TIME_STEP = TIME_STEP / N_STEPS;
    
    private static final vec3 CENTER = new vec3(0, 4, 0);
    
    
    private Renderer rRend;
    
    private Player mPlayer;
    
    private VertexData mGeom;
    
    private BodySystem mBodySystem;
    
    private Light[] mLights;
    
    private boolean mIsSimulating;
    
    private boolean mDebugDraw;
    
    
    public Simulation(Renderer aRenderer)
    {
        this.rRend = aRenderer;
        this.rRend.mTIUs.get(0).bind(Sampler.NEAREST);
        this.rRend.mTIUs.get(0).bind(Simulation.IMAGE_FACES);
        this.rRend.mShadGBuff.uniform1i("T2_ALBEDO", 0);
        
        this.mPlayer = new Player();
        {
            this.mPlayer.mCam.mOrig.set(0, 6, 9);
        }
        
        this.mGeom = new VertexData(new int[] { 3, 3, 2 }, GL11.GL_TRIANGLES, GL15.GL_STREAM_DRAW);
        
        this.mBodySystem = new BodySystem();
        {
            this.genBodies();
            
            this.genForces();
        }
        
        this.mLights = new Light[4];
        {
            this.genLights();
        }
    }
    
    
    /** Inherited from ContextThread. Called by Context in the main loop. */
    public void onTick()
    {
        if (Time.get().time() < 1.0) return; // Warm-up time.
        
        if (Keys.get().isReleased(Keys.SPACE))
        {
            this.mIsSimulating = !this.mIsSimulating;
        }
        if (Keys.get().isReleased(Keys.T))
        {
            this.mDebugDraw = !this.mDebugDraw;
        }
        
        this.mPlayer.onTick();
        
        if (this.mIsSimulating)
        {
            for (int step = 0; step < N_STEPS; step++)
            {
                this.mBodySystem.advance(SMALL_TIME_STEP);
            }
        }
        
        this.mGeom.clear();
        {
            this.mBodySystem.render(this.mGeom);
            
            if (this.mDebugDraw)
            {
                this.mBodySystem.renderDebug(this.mGeom);
            }
            
            RenderSphere.render(this.mGeom, CENTER, mat3.IDENTITY, 0.1);
        }
        this.mGeom.compileData();
        
        // Send camera data to GPU:
        mat4 m4CameraFromWorld = this.mPlayer.mCam.matCameraFromWorld();
        mat3 m3CameraFromWorld = new mat3(m4CameraFromWorld);
        this.rRend.mShadGBuff.uniform2d("V2_CAM_CLIP", this.mPlayer.mCam.mClipZ);
        this.rRend.mShadGBuff.uniformMat4("M4_CAMERA_FROM_WORLD", m4CameraFromWorld);
        this.rRend.mShadGBuff.uniformMat4("M4_CLIP_FROM_CAMERA", this.mPlayer.mCam.matClipFromCamera());
        this.rRend.mShadAccum.uniformMat3("M3_CAMERA_FROM_WORLD", m3CameraFromWorld);
        
        this.rRend.queueGeom(this.mGeom);
        this.rRend.queueLights(this.mLights);
    }
    
    /** Inherited from ContextThread. Called by Context as it terminates. */
    public void onEnd()
    {
        
    }
    
    
    
    private void genLights()
    {
        this.mLights[0] = new Light();
        this.mLights[0].mColor.set(1, 0.95, 0.8);
        this.mLights[0].mDir.set(0, -1, 0);
        this.mLights[0].mAmbient = 0.2;
        
        this.mLights[1] = new Light();
        this.mLights[1].mColor.set(0.2, 0.3, 0.5);
        this.mLights[1].mDir.set(-2, -2, -2);     this.mLights[0].mDir.norm();
        this.mLights[1].mAmbient = 0.0;
        
        this.mLights[2] = new Light();
        this.mLights[2].mColor.set(0.3, 0.5, 0.2);
        this.mLights[2].mDir.set(2, -2, -2);     this.mLights[0].mDir.norm();
        this.mLights[2].mAmbient = 0.0;
        
        this.mLights[3] = new Light();
        this.mLights[3].mColor.set(0.5, 0.2, 0.3);
        this.mLights[3].mDir.set(0, -2, 3);      this.mLights[3].mDir.norm();
        this.mLights[3].mAmbient = 0.0;
    }
    
    private void genBodies()
    {
        final int N = 4; // Number per side -1.
        final double SCALE = N + 1;
        final double DIM = 0.125 * N;
        final int LEVELS = 3; // Number of vertical levels.
        
        vec2 pnUnit = new vec2();
        for (int j = 0; j <= N; j++)
        {
            pnUnit.y = (2.0 * j) / N - 1.0;
            pnUnit.y *= SCALE;
            
            for (int i = 0; i <= N; i++)
            {
                pnUnit.x = (2.0 * i) / N - 1.0;
                pnUnit.x *= SCALE;
                
                for (int k = 0; k < LEVELS; k++)
                {
                    double levelY = 2.8 * DIM * k;
                    
                    BodyCuboid b = new BodyCuboid(math.RNG.rand3f(0.2 * DIM, DIM));
                    vec3 x = new vec3(pnUnit.x, levelY, pnUnit.y);
                    vec3 tan = new vec3(-pnUnit.y, levelY, pnUnit.x);
                    b.mX.set(x);
                    b.mP.set(tan);
                    b.mL.set(new vec3(0, -0.1675, 0));
                    b.updateVertsAndNorms();
                    b.transformInternalsToW();
                    this.mBodySystem.mBodies.add(b);
                }
            }
        }
    }
    
    private void genForces()
    {
        Force fMagn = new ForceUniformMag(
            this.mBodySystem.mBodies, 
            vec3.AXIS_YP, 
            1.0
        );
        this.mBodySystem.mForces.add(fMagn);
        
        Force fCGrav = new ForceGravCentered(
            this.mBodySystem.mBodies, 
            CENTER, 
            3.0
        );
        this.mBodySystem.mForces.add(fCGrav);
        
        Force fViscous = new ForceViscousDrag(
            this.mBodySystem.mBodies, 
            1.0e-2, 
            5.0e-2
        );
        this.mBodySystem.mForces.add(fViscous);
        
        Force fGrav = new ForceGrav(
            this.mBodySystem.mBodies, 
            0.5
        );
        this.mBodySystem.mForces.add(fGrav);
        
        Force fAxial = new ForceVortexAxial(
            this.mBodySystem.mBodies, 
            CENTER, 
            vec3.AXIS_YP, 
            1.0e-2
        );
        this.mBodySystem.mForces.add(fAxial);
        
        Force fPlanar = new ForceVortexPlanar(
            this.mBodySystem.mBodies, 
            CENTER, 
            vec3.AXIS_YP, 
            1.0e-1
        );
        this.mBodySystem.mForces.add(fPlanar);
    }
    
    
}
